package zju.ccnt.oauth2.user;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.security.core.Authentication;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.provisioning.UserDetailsManager;
import zju.ccnt.oauth2.Role;
import zju.ccnt.oauth2.SimpleUserDetails;
import zju.ccnt.rest.service.BaseService;
import zju.ccnt.oauth2.user.api.ApiUser;
import zju.ccnt.oauth2.user.api.ChangePasswordRequest;
import zju.ccnt.oauth2.user.api.CreateUserRequest;
import zju.ccnt.oauth2.user.api.PasswordRequest;
import zju.ccnt.oauth2.user.exception.DuplicateUserException;

import java.util.Collections;

/**
 * Created by zha on 14/11/5.
 */
public abstract class UserService<T extends ApiUser> extends BaseService{

    @Autowired
    @Qualifier("userDetailsManager")
    protected UserDetailsManager userDetailsManager;
    @Autowired
    @Qualifier("passwordEncoder")
    protected PasswordEncoder passwordEncoder;
    @Autowired
    @Qualifier("jdbcTokenStore")
    protected TokenStore tokenStore;

    public UserService() {
        super();
    }

    /**
     * change the password into the corresponding persistence store
     * @param newPassword
     * @param email
     *          unique email for a user
     */
    abstract public void updateUserPasswordToDB(PasswordRequest newPassword, String email);

    /**
     * get user readable detail info
     * @param email
     *     the specific ApiUser type instance
     * @return
     */
    abstract public T getUser(String email);

    /**
     * create a user into into the corresponding persistence store
     * no oauth2 involved here
     * @param createUserRequest
     * @return
     */
    abstract public T createUserToDB(CreateUserRequest<T> createUserRequest);

    /**
     * create a user, add to db & oauth
     * @param createUserRequest
     * @param role
     *        the role of this user @See Role
     * @return
     */
    public T createUser(CreateUserRequest<T> createUserRequest, Role role){
        validate(createUserRequest);
        oauth2CreateUser(createUserRequest, role);
        T apiUser = createUserToDB(createUserRequest);
        return apiUser;
    }

    /**
     *
     * @param changePasswordRequest
     * @param authentication
     */
    public void updateUserPassword(ChangePasswordRequest changePasswordRequest, Authentication authentication){
        validate(changePasswordRequest);
        updateUserPasswordToDB(changePasswordRequest.getNewPassword(), authentication.getName());
        oauth2ChangePassword(changePasswordRequest, authentication);
    }

    /**
     * add the user to oauth_user
     * @param createUserRequest
     */
    protected void oauth2CreateUser(CreateUserRequest createUserRequest, Role role){
        String email = createUserRequest.getUser().getEmail();
        //check whether the email has been registered to this platform
        if (userDetailsManager.userExists(email)) {
            throw new DuplicateUserException(email);
        }
        //add new user into auth users table
        userDetailsManager.createUser(new SimpleUserDetails(
                createUserRequest.getUser().getEmail(),
                passwordEncoder.encode(createUserRequest.getPassword().getPassword()),
                Collections.singletonList(role)
        ));
    }

    /**
     * when the password changed, the oauth info(auth_user, access_token) should be generated here
     * @param changePasswordRequest
     * @param authentication
     */
    protected void oauth2ChangePassword(ChangePasswordRequest changePasswordRequest, Authentication authentication){
        //update oauth user
        userDetailsManager.changePassword(
                changePasswordRequest.getOldPassword().getPassword(),
                passwordEncoder.encode(changePasswordRequest.getNewPassword().getPassword()));

        if (authentication instanceof OAuth2Authentication) {
            //delete the access_token
            tokenStore.removeAccessToken(
                    tokenStore.getAccessToken((OAuth2Authentication)authentication)
            );
        }

    }

}
